package org.gwu.seuss.service;

import java.util.List;
import java.util.UUID;

import javax.crypto.SecretKey;

import org.apache.commons.lang3.StringUtils;
import org.gwu.seuss.communication.crypto.CryptoUtil;
import org.gwu.seuss.communication.crypto.CryptoVal;
import org.gwu.seuss.communication.exception.FileException;
import org.gwu.seuss.communication.exception.FileIntegrityException;
import org.gwu.seuss.communication.exception.UserException;
import org.gwu.seuss.communication.request.ClientRequest;
import org.gwu.seuss.communication.request.Permissions;
import org.gwu.seuss.communication.util.FileUtils;
import org.gwu.seuss.domain.File;
import org.gwu.seuss.domain.Shared;
import org.gwu.seuss.domain.User;
import org.gwu.seuss.persistence.FilesMapper;
import org.gwu.seuss.persistence.UsersMapper;

public class FileServiceImpl extends ServiceBase implements FileService {

	public FileServiceImpl() {

	}

	public FileServiceImpl(FilesMapper filesMapper, UsersMapper usersMapper,
			CryptoUtil cryptoUtil) {
		this.cryptoUtil = cryptoUtil;
		this.filesMapper = filesMapper;
		this.usersMapper = usersMapper;
	}

	@Override
	public int insertFile(File file) {
		return super.filesMapper.insertFile(file);
	}

	@Override
	public List<File> selectAllFileByOwner(int ownerId) {
		return super.filesMapper.selectAllFileByOwner(ownerId);
	}

	@Override
	public File selectFile(int ownerId, String filename) {
		return super.filesMapper.selectFile(ownerId, filename);
	}

	@Override
	public int deleteFile(String owner, String filename) throws Exception {
		User user = usersMapper.selectUserByEmail(owner);
		int result = filesMapper.deleteFile(user.getUserId(), filename);

		if (result > 0) {
			FileUtils.deleteFile(filename);
		} else {
			throw new Exception("Failed to delete " + filename);
		}

		return result;
	}

	@Override
	public byte[] downloadFile(ClientRequest req) throws Exception {
		String username = req.getUsername();

		File file = this.selectFileById(req.getFileId());

		if (file == null) {
			log.error("File does not exist with id " + req.getFileId());
			throw new FileException("File does not exist.");
		}

		log.info("User " + username + " requested to download file "
				+ req.getFileId());

		// Find file on local disk
		byte[] encFile = FileUtils.readFile(file.getUuidFilename().toString());

		User user = usersMapper.selectUserByEmail(username);
		if (user == null) {
			throw new UserException("User does not exist");
		}

		// Obtain the secret key to decrypt file
		SecretKey loadedKey = cryptoUtil.getSecretKey();

		// Obtain hash from encrypted file
		String encHash = cryptoUtil.generateHmac(loadedKey, encFile);

		// verify hash
		if (!StringUtils.equals(file.getMac(), encHash)) {
			// Hashes do not match
			log.error("The hash check failed");
			throw new FileIntegrityException("File hash does not match");
		} else {
			// Decrypt
			return cryptoUtil.decrypt(file.getIv(), loadedKey, encFile);
		}
	}

	@Override
	public int uploadFile(ClientRequest req) throws Exception {
		User user = usersMapper.selectUserByEmail(req.getUsername());
		if (user == null) {
			throw new UserException("User does not exist");
		}

		// Obtain the secret key to encrypt file
		SecretKey loadedKey = cryptoUtil.getSecretKey();

		// Encrypt the file
		CryptoVal val = cryptoUtil.encrypt(loadedKey, req.getFileBytes());

		// Obtain the HMAC
		String hash = cryptoUtil.generateHmac(loadedKey, val.getEncFile());

		log.info("Uploaded encrypted file " + req.getFileName());

		int fileId = req.getFileId();

		if (fileId > -1) {
			// Get the existing file from database
			File file = filesMapper.selectFileById(fileId);

			// Overwrite the file
			FileUtils.writeFile(file.getUuidFilename(), val.getEncFile());

			return filesMapper.updateFileHmac(fileId, hash, val.getIv());
		} else {
			// Save new file to local disk
			UUID uFilename = UUID.randomUUID();

			FileUtils.writeFile(uFilename.toString(), val.getEncFile());

			// Save the file record to DB
			File file = new File();
			file.setOwnerId(user.getUserId());
			file.setFilename(req.getFileName());
			file.setUuidFilename(uFilename.toString());
			file.setIv(val.getIv());
			file.setMac(hash);
			file.setPath("");
			file.setDeleted(false);

			return insertFile(file);
		}
	}

	@Override
	public int shareFile(ClientRequest req) throws UserException {
		User sharedUser = this.usersMapper.selectUserByEmail(req.getUser()
				.getUsername());
		if (sharedUser == null) {
			throw new UserException("No user found with username: "
					+ req.getUser().getUsername());
		}
		if (req.getPermission().equals(Permissions.REMOVE_ALL)) {
			for (Shared shared : this.sharedMapper
					.selectAllSharedByUserIdAndFileId(sharedUser.getUserId(),
							req.getFileId())) {
				this.sharedMapper.hardDeleteSharedBySharedId(shared
						.getSharedId());
			}
			return 1;
		} else {
			Shared sharedPermission = new Shared();

			sharedPermission.setFileId(req.getFileId());
			sharedPermission.setSharedUserId(sharedUser.getUserId());
			sharedPermission.setPermissionId(req.getPermission().getValue());

			return this.sharedMapper.insertShared(sharedPermission);
		}
	}

	@Override
	public File selectFileById(int fileId) {
		return this.filesMapper.selectFileById(fileId);
	}
}